Load packages

Load RL URD object

We removed presumably non-r1 derived cells.

urd_RL <- readRDS(file="./data/urd_RL.rds")
non_r1.cell.group <- c("Tlx3","Isl1","Is","10")
cell.removed <- cellsInCluster(urd_RL, "ident", non_r1.cell.group)
length(cell.removed)
[1] 136
cells.keep <- setdiff(colnames(urd@logupx.data), cell.removed)
length(cells.keep)
[1] 1818
urd.trimmed <- urdSubset(urd_RL, cells.keep=cells.keep)
plotDim(urd.trimmed, "ident",label.clusters = T, legend = F)

saveRDS(urd.trimmed, "./data/urd_trimmed.rds")

URD analysis

This step will take some time. We will skip this one and load the previously saved object.

Load previously saved objects

urd <- readRDS("./data/urd_trimmed.rds")
# Define C1 cells as root cells 
plotDimHighlight(urd, "ident", "C1", legend=F)

root.cells <- cellsInCluster(urd, "ident", c("C1"))
dm <- readRDS("./data/dm.s16_n75.rds")
flood.result <- readRDS(file="./data/flood-dm.s16-random200.rds")
urd <- importDM(urd, dm)
urd <- floodPseudotimeProcess(urd, flood.result, floods.name="pseudotime", max.frac.NA=0.4, pseudotime.fun=mean, stability.div=20)
### Inspect pseudotime
p0 <- pseudotimePlotStabilityOverall(urd) + 
  geom_hline(yintercept = 2.5, linetype="dashed", color = "red")
p1 <- plotDim(urd, "ident", legend=F, plot.title="Putative tip cells", alpha=1,label.clusters = T)
p2 <- plotDim(urd, "pseudotime", plot.title = "Pseudotime",legend=F)
print(plot_grid(p0,p1,p2, ncol = 3))

# Create a data.frame that includes pseudotime and stage information
gg.data <- cbind(urd@pseudotime, urd@meta[rownames(urd@pseudotime),])
gg.data$tip.Name <- urd@group.ids[rownames(gg.data),]$ident
# Plot pseudotime
ggplot(gg.data, aes(x=pseudotime, color=tip.Name, fill=tip.Name)) + geom_density(alpha=0.4) + theme_bw()+theme(legend.position="none")+
  facet_wrap( ~ tip.Name, ncol=6)

ggplot(gg.data, aes(x=pseudotime, color=tip.Name, fill=tip.Name)) + geom_density(alpha=0.4) + theme_bw()+theme(legend.position="none")

# pass the cluster to claster name
urd@group.ids$tip.name <- NULL
urd@group.ids$tip.name[urd@group.ids$ident== "mCN"] <- "mCN"
urd@group.ids$tip.name[urd@group.ids$ident== "lCN"] <- "lCN"
urd@group.ids$tip.name[urd@group.ids$ident== "GC"] <- "GC"
urd@group.ids$class <- NULL
urd@group.ids$class[!is.na(urd@group.ids$tip.name)] <- "Tip"
urd@group.ids$tip.num <- NA
urd@group.ids$tip.num[urd@group.ids$ident== "mCN"] <- "3"
urd@group.ids$tip.num[urd@group.ids$ident== "lCN"] <- "2"
urd@group.ids$tip.num[urd@group.ids$ident== "GC"] <- "1"
saveRDS(urd, file="./data/urd.rds")

Biased random walks from each tip

# Define parameters of logistic function to bias transition probabilities
diffusion.logistic <- pseudotimeDetermineLogistic(urd, "pseudotime", optimal.cells.forward=20, max.cells.back=40, pseudotime.direction="<", do.plot=T, print.values=T)
[1] "Mean pseudotime back (~40 cells) 0.0139819579440117"
[1] "Chance of accepted move to equal pseudotime is 0.82442639407355"
[1] "Mean pseudotime forward (~20 cells) -0.00695152465444183"

biased.tm <- pseudotimeWeightTransitionMatrix(urd, pseudotime = "pseudotime", logistic.params = diffusion.logistic, pseudotime.direction = "<")
# We ran this in the HPC cluster
# # # Simulate the biased random walks from each tip
# walks <- simulateRandomWalksFromTips(urd, tip.group.id = "tip.num", root.cells = root.cells,
#                                      transition.matrix = biased.tm, n.per.tip = 25000, root.visits = 1,
#                                      max.steps = 5000, verbose = T)
# saveRDS(walks, file = "./data/walks_n25000.rds")
# retrieve previously calculated random walks results
walks <- readRDS("./data/walks_n25000.rds")
# Process the biased random walks into visitation frequencies
urd <- processRandomWalksFromTips(urd, walks, verbose = T)
[1] "2019-01-22 13:51:08 - Processing walks from tip 1"
[1] "Calculating pseudotime with 2500 walks."
[1] "Calculating pseudotime with 5000 walks."
[1] "Calculating pseudotime with 7500 walks."
[1] "Calculating pseudotime with 10000 walks."
[1] "Calculating pseudotime with 12500 walks."
[1] "Calculating pseudotime with 15000 walks."
[1] "Calculating pseudotime with 17500 walks."
[1] "Calculating pseudotime with 20000 walks."
[1] "Calculating pseudotime with 22500 walks."
[1] "Calculating pseudotime with 25000 walks."
[1] "2019-01-22 13:51:13 - Processing walks from tip 2"
[1] "Calculating pseudotime with 2500 walks."
[1] "Calculating pseudotime with 5000 walks."
[1] "Calculating pseudotime with 7500 walks."
[1] "Calculating pseudotime with 10000 walks."
[1] "Calculating pseudotime with 12500 walks."
[1] "Calculating pseudotime with 15000 walks."
[1] "Calculating pseudotime with 17500 walks."
[1] "Calculating pseudotime with 20000 walks."
[1] "Calculating pseudotime with 22500 walks."
[1] "Calculating pseudotime with 25000 walks."
[1] "2019-01-22 13:51:19 - Processing walks from tip 3"
[1] "Calculating pseudotime with 2500 walks."
[1] "Calculating pseudotime with 5000 walks."
[1] "Calculating pseudotime with 7500 walks."
[1] "Calculating pseudotime with 10000 walks."
[1] "Calculating pseudotime with 12500 walks."
[1] "Calculating pseudotime with 15000 walks."
[1] "Calculating pseudotime with 17500 walks."
[1] "Calculating pseudotime with 20000 walks."
[1] "Calculating pseudotime with 22500 walks."
[1] "Calculating pseudotime with 25000 walks."
saveRDS(urd, file="./data/urd.rds")
gridExtra::grid.arrange(grobs=list(
  plotDim(urd, "tip.name", plot.title="Cells in each tip"),
  plotDim(urd, "visitfreq.log.1", transitions.plot=10000, plot.title="visitfreq_GC"),
  plotDim(urd, "visitfreq.log.2", transitions.plot=10000, plot.title="visitfreq_mCN"),
  plotDim(urd, "visitfreq.log.3", transitions.plot=10000, plot.title="visitfreq_lCN")
))

Build trajectory tree

urd.tree <- loadTipCells(urd, "tip.num") 
tip.id <- setdiff(unique(urd.tree@group.ids$tip.num),c(NA))
urd.tree <- buildTree(urd.tree, pseudotime = "pseudotime", tips.use = tip.id, divergence.method = "ks", 
                      cells.per.pseudotime.bin = 30, bins.per.pseudotime.window = 8, minimum.visits = 10,
                      visit.threshold = 0.7, use.only.original.tips = T,
                      save.all.breakpoint.info = T, p.thresh=0.05)
[1] "Calculating divergence between 1 and 3 (Pseudotime 0 to 0.591)"
[1] "Calculating divergence between 1 and 2 (Pseudotime 0 to 0.591)"
[1] "Calculating divergence between 3 and 2 (Pseudotime 0 to 0.62)"
[1] "Joining segments 3 and 2 at pseudotime 0.573 to create segment 4"
[1] "Calculating divergence between 1 and 4 (Pseudotime 0 to 0.573)"
[1] "Joining segments 1 and 4 at pseudotime 0.462 to create segment 5"
[1] "Assigning cells to segments."
73 cells were not visited by a branch that exists at their pseudotime and were not assigned.
[1] "Collapsing short segments."
[1] "Removing singleton segments."
[1] "Reassigning cells to segments."
73 cells were not visited by a branch that exists at their pseudotime and were not assigned.
[1] "Assigning cells to nodes."
[1] "Laying out tree."
[1] "Adding cells to tree."
# Name the segments
urd.tree <- nameSegments(urd.tree, segments= tip.id, 
                         segment.names = c("GC", "mCN","lCN"), 
                         short.names = tip.id)

Plot results

plotTree(urd.tree, "segment", title="URD tree segment", label.segments = T)

plotTree(urd.tree, "ident", title="URD cluster ID", label.segments = T)

Plot gene expression over the trajectory

gridExtra::grid.arrange(grobs=list(
  plotTree(urd.tree, "Lmx1a", title="mCN marker_Lmx1a"),
  plotTree(urd.tree, "Olig2", title="lCN marker_Olig2"),
  plotTree(urd.tree, "Atoh1", title="GC marker_Atoh1"),
  plotTree(urd.tree, "Wnt1", title="C1 marker_Wnt1")
))

saveRDS(urd.tree, file = "./data/urdTree.rds")

Examine gene expression cascades

urd.tree <- readRDS("./data/urdTree.rds")

colnames(urd.tree@meta) <- c("n.Genes","n.Trans","orig.ident","percent.mito","tree.ident","clust")

tips.to.run <- setdiff(as.character(urd.tree@tree$segment.names), NA)
genes.use <- NULL # Calculate for all genes

# Calculate the markers of each other population.
gene.markers <- list()
markers.sum <- NULL
for (tip in tips.to.run) {
  print(paste0(Sys.time(), ": ", tip))
  markers <- aucprTestAlongTree(urd.tree, pseudotime = "pseudotime", tips = tip, log.effect.size = 0.4,
                                auc.factor = 1.25, max.auc.threshold = 0.85, frac.must.express = 0.1,
                                frac.min.diff = 0.1, genes.use = genes.use, root = NULL,
                                segs.to.skip = NULL, only.return.global = F, must.beat.sibs = 0.6,
                                report.debug = T)
  gene.markers[[tip]] <- markers
}
saveRDS(gene.markers, "./data/geneMarkers.rds")

# Separate actual marker lists from the stats lists
gene.markers.de <- lapply(gene.markers, function(x) x[[1]])
gene.markers.stats <- lapply(gene.markers, function(x) x[[2]])
names(gene.markers.de) <- names(gene.markers)
names(gene.markers.stats) <- names(gene.markers)

# Compile all comparison stats into a single table
all.de.stats <- do.call("rbind", gene.markers.stats)
all.de.stats$tip <- substr(rownames(all.de.stats),1,nchar(rownames(all.de.stats))-2)

# Do a few plots
p1 <- ggplot(all.de.stats, aes(x=pt.1.mean, y=pt.2.mean)) + geom_point() + theme_bw() + geom_abline(slope = 1, intercept=0, col='red', lty=2) + labs(x="Mean Pseudotime (Group 1)", y="Mean Pseudotime (Group 2)")
p2 <- ggplot(all.de.stats, aes(x=genes.1.mean, y=genes.2.mean)) + geom_point() + theme_bw() + geom_abline(slope = 1, intercept=0, col='red', lty=2) + labs(x="Mean Detected Genes (Group 1)", y="Mean Detected Genes (Group 2)")
p3 <- ggplot(all.de.stats, aes(x=trans.1.mean, y=trans.2.mean)) + geom_point() + theme_bw() + geom_abline(slope = 1, intercept=0, col='red', lty=2) + labs(x="Mean Transcripts (Group 1)", y="Mean Transcripts (Group 2)")

plot_grid(p1,p2,p3, ncol = 3)


# Create a fold to hold the results
path <- ".data/"
dir.create(paste0(path,"cascades"))

# Generate impulse fits
gene.cascades <- lapply(tips.to.run, function(tip) {
  print(paste0(Sys.time(), ": Impulse Fit ", tip))
  seg.cells <- cellsAlongLineage(urd.tree, tip, remove.root=F)
  casc <- geneCascadeProcess(object = urd.tree, pseudotime='pseudotime', cells = seg.cells, genes= rownames(gene.markers.de[[tip]]), 
                             moving.window=3, cells.per.window=10, limit.single.sigmoid.slopes = "on", verbose = T)
  tip.file.name <- gsub("/", "_", tip)
  saveRDS(casc, file=paste0(path, "cascades/casc_", tip.file.name, ".rds"))
  return(casc)
})
names(gene.cascades) <- tips.to.run

# Make a heatmap of every cascade in a single PDF.
for (tip in tips.to.run) {
  gene.num <- nrow(gene.cascades[[tip]]$scaled.expression)
  geneCascadeHeatmap(cascade=gene.cascades[[tip]], title = tip, row.font.size = 0.06*gene.num)
}

# =====repeat with TF genes only ============
# Identify DE genes of each other population (restricted to transcription factors only)
mGenes <- readRDS("/Volumes/jali/Genome/Annotation1.rds")
TF.use <- intersect(rownames(urd@logupx.data),mGenes[which(mGenes$Type > "nonTF"), "mgi_symbol"]);length(TF.use)

TF.markers <- list()
for (tipn in 1:length(tips.to.run)) {
  tip <- tips.to.run[tipn]
  print(paste0(Sys.time(), ": ", tip))
  markers <- aucprTestAlongTree(urd.tree, pseudotime = "pseudotime", tips = tip, log.effect.size = 0.25,
                                auc.factor = 1.25, max.auc.threshold = 0.85, frac.must.express = 0.1,
                                frac.min.diff = 0.1, genes.use = TF.use, root = NULL,
                                segs.to.skip = NULL, only.return.global = F, must.beat.sibs = 0.6,
                                report.debug = T)
  TF.markers[[tip]] <- markers
}
saveRDS(TF.markers, "./data/geneMarkers_TF.rds")

# ====== Generate impulse fits for DE transcription factors ====== 
TF.markers.de <- lapply(TF.markers, function(x) x[[1]])
names(TF.markers.de) <- names(TF.markers)

TF.cascades <- lapply(tips.to.run, function(tip) {
  print(paste0(Sys.time(), ": Impulse Fit ", tip))
  seg.cells <- cellsAlongLineage(urd.tree, tip, remove.root=F)
  casc <- geneCascadeProcess(object = urd.tree, pseudotime='pseudotime', cells = seg.cells, genes= rownames(TF.markers.de[[tip]]), 
                             # background.genes = sample(setdiff(rownames(urd.tree@logupx.data),urd.tree@var.genes), 1000),
                             moving.window=3, cells.per.window=10, limit.single.sigmoid.slopes = "on", verbose = T)
  return(casc)
})
names(TF.cascades) <- tips.to.run
saveRDS(TF.cascades, "./data/TF.cascades.rds")

# Make a heatmap of every cascade in a single PDF.
pdf(file=paste0(path,"cascades/cascades_TF.pdf"), width=5, height=6)
for (tip in tips.to.run) {
  gene.num <- nrow(TF.cascades[[tip]]$scaled.expression)
  geneCascadeHeatmap(cascade=TF.cascades[[tip]], title = tip, row.font.size = 0.6*gene.num)
}
dev.off()

# create a table of markers genes with timing
markers.sum <- NULL
for (tip in tips.to.run) {
  res <- gene.markers.de[[tip]]
  res$tip <- tip
  cascade=gene.cascades[[tip]]
  
  # Correct for NA timings
  timing <- cascade$timing
  timing[intersect(which(is.na(timing$time.on)), which(is.infinite(timing$time.off))), "time.on"] <- Inf
  res <- cbind(res,timing)
  gene.order <- order(timing$time.on, timing$time.off, na.last=F)
  res <- res[gene.order,]
  res$gene <- rownames(res)
  res$TF <- mGenes$Type[match(res$gene,mGenes$mgi_symbol)]
  res$description <- mGenes$description[match(res$gene,mGenes$mgi_symbol)]
  markers.sum <- rbind(markers.sum, res)
}
head(markers.sum)


### ====== Identify markers of each lineage ====== 
markers.sum <- NULL
tip <- "GC"
tip.file.name <- gsub("/", "_", tip)
cascade <- readRDS(file = paste0("./cascades/casc_", tip.file.name, ".rds"))
markers <- rownames(cascade$scaled.expression)

# Determine which genes are also global markers
cells <- cellsInCluster(urd.tree, "segment", c("1"))
markers.global <- markersAUCPR(urd.tree, cells.1 = cells, cells.2 = NULL, genes.use = markers, clustering = "segment")
marker.thresh <- aucprThreshold(cells.1 = cells, cells.2 = setdiff(unlist(urd.tree@tree$cells.in.segment), cells), factor = 2.5, max.auc = Inf) # lower the stringency by reducing the factor 
de.markers <- markers.global[markers.global$AUCPR >= marker.thresh,];nrow(de.markers)
de.markers$tip <- tip
de.markers$gene <- rownames(de.markers)
de.markers$TF <- mGenes$Type[match(de.markers$gene,mGenes$mgi_symbol)]
de.markers$description <- mGenes$description[match(de.markers$gene,mGenes$mgi_symbol)]
markers.sum <- rbind(markers.sum, de.markers)

# find marker of caudal thalamus
tip <- "lCN"
tip.file.name <- gsub("/", "_", tip)
cascade <- readRDS(file = paste0("./cascades/casc_", tip.file.name, ".rds"))
markers <- rownames(cascade$scaled.expression)
cells <- cellsInCluster(urd.tree, "segment", c("2"))
markers.global <- markersAUCPR(urd.tree, cells.1 = cells, genes.use = markers, clustering = "segment")
marker.thresh <- aucprThreshold(cells.1 = cells, cells.2 = setdiff(unlist(urd.tree@tree$cells.in.segment), cells), factor = 2.5, max.auc = Inf) 
de.markers <- markers.global[markers.global$AUCPR >= marker.thresh,];nrow(de.markers)
de.markers$tip <- tip
de.markers$gene <- rownames(de.markers)
de.markers$TF <- mGenes$Type[match(de.markers$gene,mGenes$mgi_symbol)]
de.markers$description <- mGenes$description[match(de.markers$gene,mGenes$mgi_symbol)]
markers.sum <- rbind(markers.sum, de.markers)

# find marker of caudal thalamus
tip <- "mCN"
tip.file.name <- gsub("/", "_", tip)
cascade <- readRDS(file = paste0("./cascades/casc_", tip.file.name, ".rds"))
markers <- rownames(cascade$scaled.expression)
cells <- cellsInCluster(urd.tree, "segment", c("3"))
markers.global <- markersAUCPR(urd.tree, cells.1 = cells, cells.2 = NULL, genes.use = markers, clustering = "segment")
marker.thresh <- aucprThreshold(cells.1 = cells, cells.2 = setdiff(unlist(urd.tree@tree$cells.in.segment), cells), 
                                factor = 2.5, max.auc = Inf) # lower the stringency by reducing the factor 
de.markers <- markers.global[markers.global$AUCPR >= marker.thresh,];nrow(de.markers)
de.markers$tip <- tip
de.markers$gene <- rownames(de.markers)
de.markers$TF <- mGenes$Type[match(de.markers$gene,mGenes$mgi_symbol)]
de.markers$description <- mGenes$description[match(de.markers$gene,mGenes$mgi_symbol)]
markers.sum <- rbind(markers.sum, de.markers)

# openxlsx::write.xlsx(markers.sum, file = "./tables/segmantMarkers_all.xlsx")
openxlsx::write.xlsx(markers.sum, file = "./tables/lineageMarkers_all.xlsx")
LS0tCnRpdGxlOiAiQ2VsbCBjbHVzdGVyaW5nIG9mIHRoZSBSTCBsaW5lYWdlIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKbGluZXN0cmV0Y2g6IDAuNQotLS0KXGZvbnRzaXplezh9ezE4fQoKIyBMb2FkIHBhY2thZ2VzCmBgYHtyIGxvYWQtcGFja2FnZXMsIGluY2x1ZGU9RkFMU0UsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQpsaWJyYXJ5KFVSRCk7bGlicmFyeShjb3dwbG90KQpgYGAKCiMgTG9hZCBSTCBVUkQgb2JqZWN0CldlIHJlbW92ZWQgcHJlc3VtYWJseSBub24tcjEgZGVyaXZlZCBjZWxscy4gCmBgYHtyIGZpZy53aWR0aD0zLCBmaWcuYXNwPTF9CnVyZF9STCA8LSByZWFkUkRTKGZpbGU9Ii4vZGF0YS91cmRfUkwucmRzIikKCm5vbl9yMS5jZWxsLmdyb3VwIDwtIGMoIlRseDMiLCJJc2wxIiwiSXMiLCIxMCIpCmNlbGwucmVtb3ZlZCA8LSBjZWxsc0luQ2x1c3Rlcih1cmRfUkwsICJpZGVudCIsIG5vbl9yMS5jZWxsLmdyb3VwKQpsZW5ndGgoY2VsbC5yZW1vdmVkKQpjZWxscy5rZWVwIDwtIHNldGRpZmYoY29sbmFtZXModXJkQGxvZ3VweC5kYXRhKSwgY2VsbC5yZW1vdmVkKQpsZW5ndGgoY2VsbHMua2VlcCkKCnVyZC50cmltbWVkIDwtIHVyZFN1YnNldCh1cmRfUkwsIGNlbGxzLmtlZXA9Y2VsbHMua2VlcCkKcGxvdERpbSh1cmQudHJpbW1lZCwgImlkZW50IixsYWJlbC5jbHVzdGVycyA9IFQsIGxlZ2VuZCA9IEYpCmBgYAoKYGBge3J9CnNhdmVSRFModXJkLnRyaW1tZWQsICIuL2RhdGEvdXJkX3RyaW1tZWQucmRzIikKYGBgCgojIFVSRCBhbmFseXNpcwpUaGlzIHN0ZXAgd2lsbCB0YWtlIHNvbWUgdGltZS4gV2Ugd2lsbCBza2lwIHRoaXMgb25lIGFuZCBsb2FkIHRoZSBwcmV2aW91c2x5IHNhdmVkIG9iamVjdC4KYGBge3J9CnVyZCA8LSByZWFkUkRTKCIuL2RhdGEvdXJkX3RyaW1tZWQucmRzIikKCiMgZXN0aW1hdGUgdGhlIGtubiB2YWx1ZSB1c2luZyBzcXJ0KG4uY2VsbHMpCnNxcnQobmNvbCh1cmRAbG9ndXB4LmRhdGEpKQoKI1Rlc3QgZGlmZmVyZW50IHNpZ21hIGZvciBkaWZmdXNpb24gbWFwCmZvciAocyBpbiBjKDg6MTYpKSB7CiAgdXJkIDwtIGNhbGNETSh1cmQsIGtubiA9IDc1LCBzaWdtYS51c2UgPSBzKQogIGRtIDwtIHVyZEBkbQogIAogIHBkZihwYXN0ZTAoIi4vZmlndXJlcy9ETWFycmF5X3MiLHMsIl9uNzUucGRmIiksIGg9IDEwLCB3ID0gMTApCiAgcGxvdERpbUFycmF5KG9iamVjdCA9IHVyZCwgcmVkdWN0aW9uLnVzZSA9ICJkbSIsIGRpbXMudG8ucGxvdCA9IDE6MTgsIGxhYmVsPSJpZGVudCIsIHBsb3QudGl0bGU9IiIsCiAgICAgICAgICAgICAgIG91dGVyLnRpdGxlPXBhc3RlMCgiQ2x1c3RlciAtIERpZmZ1c2lvbiBNYXAgc2lnbWEgPSAiLHMsIiwgayA9IDc1IiksIGxlZ2VuZD1GLCBhbHBoYT0wLjcpCiAgZGV2Lm9mZigpCiAgc2F2ZVJEUyhkbSwgZmlsZSA9IHBhc3RlMCgiLi9kYXRhL2RtLnMiLHMsIl9uNzUucmRzIikpCn0KCmRtIDwtIHJlYWRSRFMoIi4vZGF0YS9kbS5zMTZfbjc1LnJkcyIpCnVyZCA8LSBpbXBvcnRETSh1cmQsIGRtKQoKIyBEZWZpbmUgQzEgY2VsbHMgYXMgcm9vdCBjZWxscyAKcGxvdERpbUhpZ2hsaWdodCh1cmQsICJpZGVudCIsICJDMSIsIGxlZ2VuZD1GKQpyb290LmNlbGxzIDwtIGNlbGxzSW5DbHVzdGVyKHVyZCwgImlkZW50IiwgYygiQzEiKSkKCgpmbG9vZC5yZXN1bHQgPC0gZmxvb2RQc2V1ZG90aW1lKHVyZCwgcm9vdC5jZWxscz1yb290LmNlbGxzLCBuPTIwMCwgbWluaW11bS5jZWxscy5mbG9vZGVkPTIsIHZlcmJvc2U9VCkKc2F2ZVJEUyhmbG9vZC5yZXN1bHQsIGZpbGU9Ii4vZGF0YS9mbG9vZC1kbS5zMTYtcmFuZG9tMjAwLnJkcyIpCmBgYAoKCiMgTG9hZCBwcmV2aW91c2x5IHNhdmVkIG9iamVjdHMKYGBge3J9CnVyZCA8LSByZWFkUkRTKCIuL2RhdGEvdXJkX3RyaW1tZWQucmRzIikKIyBEZWZpbmUgQzEgY2VsbHMgYXMgcm9vdCBjZWxscyAKcGxvdERpbUhpZ2hsaWdodCh1cmQsICJpZGVudCIsICJDMSIsIGxlZ2VuZD1GKQpyb290LmNlbGxzIDwtIGNlbGxzSW5DbHVzdGVyKHVyZCwgImlkZW50IiwgYygiQzEiKSkKCmRtIDwtIHJlYWRSRFMoIi4vZGF0YS9kbS5zMTZfbjc1LnJkcyIpCmZsb29kLnJlc3VsdCA8LSByZWFkUkRTKGZpbGU9Ii4vZGF0YS9mbG9vZC1kbS5zMTYtcmFuZG9tMjAwLnJkcyIpCgp1cmQgPC0gaW1wb3J0RE0odXJkLCBkbSkKdXJkIDwtIGZsb29kUHNldWRvdGltZVByb2Nlc3ModXJkLCBmbG9vZC5yZXN1bHQsIGZsb29kcy5uYW1lPSJwc2V1ZG90aW1lIiwgbWF4LmZyYWMuTkE9MC40LCBwc2V1ZG90aW1lLmZ1bj1tZWFuLCBzdGFiaWxpdHkuZGl2PTIwKQoKIyMjIEluc3BlY3QgcHNldWRvdGltZQpwMCA8LSBwc2V1ZG90aW1lUGxvdFN0YWJpbGl0eU92ZXJhbGwodXJkKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDIuNSwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gInJlZCIpCnAxIDwtIHBsb3REaW0odXJkLCAiaWRlbnQiLCBsZWdlbmQ9RiwgcGxvdC50aXRsZT0iUHV0YXRpdmUgdGlwIGNlbGxzIiwgYWxwaGE9MSxsYWJlbC5jbHVzdGVycyA9IFQpCnAyIDwtIHBsb3REaW0odXJkLCAicHNldWRvdGltZSIsIHBsb3QudGl0bGUgPSAiUHNldWRvdGltZSIsbGVnZW5kPUYpCgpwcmludChwbG90X2dyaWQocDAscDEscDIsIG5jb2wgPSAzKSkKCiMgQ3JlYXRlIGEgZGF0YS5mcmFtZSB0aGF0IGluY2x1ZGVzIHBzZXVkb3RpbWUgYW5kIHN0YWdlIGluZm9ybWF0aW9uCmdnLmRhdGEgPC0gY2JpbmQodXJkQHBzZXVkb3RpbWUsIHVyZEBtZXRhW3Jvd25hbWVzKHVyZEBwc2V1ZG90aW1lKSxdKQpnZy5kYXRhJHRpcC5OYW1lIDwtIHVyZEBncm91cC5pZHNbcm93bmFtZXMoZ2cuZGF0YSksXSRpZGVudAoKIyBQbG90IHBzZXVkb3RpbWUKZ2dwbG90KGdnLmRhdGEsIGFlcyh4PXBzZXVkb3RpbWUsIGNvbG9yPXRpcC5OYW1lLCBmaWxsPXRpcC5OYW1lKSkgKyBnZW9tX2RlbnNpdHkoYWxwaGE9MC40KSArIHRoZW1lX2J3KCkrdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgZmFjZXRfd3JhcCggfiB0aXAuTmFtZSwgbmNvbD02KQpnZ3Bsb3QoZ2cuZGF0YSwgYWVzKHg9cHNldWRvdGltZSwgY29sb3I9dGlwLk5hbWUsIGZpbGw9dGlwLk5hbWUpKSArIGdlb21fZGVuc2l0eShhbHBoYT0wLjQpICsgdGhlbWVfYncoKSt0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQpgYGAKCmBgYHtyfQojIHBhc3MgdGhlIGNsdXN0ZXIgdG8gY2xhc3RlciBuYW1lCnVyZEBncm91cC5pZHMkdGlwLm5hbWUgPC0gTlVMTAp1cmRAZ3JvdXAuaWRzJHRpcC5uYW1lW3VyZEBncm91cC5pZHMkaWRlbnQ9PSAibUNOIl0gPC0gIm1DTiIKdXJkQGdyb3VwLmlkcyR0aXAubmFtZVt1cmRAZ3JvdXAuaWRzJGlkZW50PT0gImxDTiJdIDwtICJsQ04iCnVyZEBncm91cC5pZHMkdGlwLm5hbWVbdXJkQGdyb3VwLmlkcyRpZGVudD09ICJHQyJdIDwtICJHQyIKCnVyZEBncm91cC5pZHMkY2xhc3MgPC0gTlVMTAp1cmRAZ3JvdXAuaWRzJGNsYXNzWyFpcy5uYSh1cmRAZ3JvdXAuaWRzJHRpcC5uYW1lKV0gPC0gIlRpcCIKCnVyZEBncm91cC5pZHMkdGlwLm51bSA8LSBOQQp1cmRAZ3JvdXAuaWRzJHRpcC5udW1bdXJkQGdyb3VwLmlkcyRpZGVudD09ICJtQ04iXSA8LSAiMyIKdXJkQGdyb3VwLmlkcyR0aXAubnVtW3VyZEBncm91cC5pZHMkaWRlbnQ9PSAibENOIl0gPC0gIjIiCnVyZEBncm91cC5pZHMkdGlwLm51bVt1cmRAZ3JvdXAuaWRzJGlkZW50PT0gIkdDIl0gPC0gIjEiCmBgYAoKYGBge3J9CnNhdmVSRFModXJkLCBmaWxlPSIuL2RhdGEvdXJkLnJkcyIpCmBgYAoKIyBCaWFzZWQgcmFuZG9tIHdhbGtzIGZyb20gZWFjaCB0aXAgCmBgYHtyfQojIERlZmluZSBwYXJhbWV0ZXJzIG9mIGxvZ2lzdGljIGZ1bmN0aW9uIHRvIGJpYXMgdHJhbnNpdGlvbiBwcm9iYWJpbGl0aWVzCmRpZmZ1c2lvbi5sb2dpc3RpYyA8LSBwc2V1ZG90aW1lRGV0ZXJtaW5lTG9naXN0aWModXJkLCAicHNldWRvdGltZSIsIG9wdGltYWwuY2VsbHMuZm9yd2FyZD0yMCwgbWF4LmNlbGxzLmJhY2s9NDAsIHBzZXVkb3RpbWUuZGlyZWN0aW9uPSI8IiwgZG8ucGxvdD1ULCBwcmludC52YWx1ZXM9VCkKCmJpYXNlZC50bSA8LSBwc2V1ZG90aW1lV2VpZ2h0VHJhbnNpdGlvbk1hdHJpeCh1cmQsIHBzZXVkb3RpbWUgPSAicHNldWRvdGltZSIsIGxvZ2lzdGljLnBhcmFtcyA9IGRpZmZ1c2lvbi5sb2dpc3RpYywgcHNldWRvdGltZS5kaXJlY3Rpb24gPSAiPCIpCgojIFdlIHJhbiB0aGlzIGluIHRoZSBIUEMgY2x1c3RlcgojICMgIyBTaW11bGF0ZSB0aGUgYmlhc2VkIHJhbmRvbSB3YWxrcyBmcm9tIGVhY2ggdGlwCiMgd2Fsa3MgPC0gc2ltdWxhdGVSYW5kb21XYWxrc0Zyb21UaXBzKHVyZCwgdGlwLmdyb3VwLmlkID0gInRpcC5udW0iLCByb290LmNlbGxzID0gcm9vdC5jZWxscywKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNpdGlvbi5tYXRyaXggPSBiaWFzZWQudG0sIG4ucGVyLnRpcCA9IDI1MDAwLCByb290LnZpc2l0cyA9IDEsCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5zdGVwcyA9IDUwMDAsIHZlcmJvc2UgPSBUKQojIHNhdmVSRFMod2Fsa3MsIGZpbGUgPSAiLi9kYXRhL3dhbGtzX24yNTAwMC5yZHMiKQpgYGAKCgpgYGB7cn0KIyByZXRyaWV2ZSBwcmV2aW91c2x5IGNhbGN1bGF0ZWQgcmFuZG9tIHdhbGtzIHJlc3VsdHMKd2Fsa3MgPC0gcmVhZFJEUygiLi9kYXRhL3dhbGtzX24yNTAwMC5yZHMiKQoKIyBQcm9jZXNzIHRoZSBiaWFzZWQgcmFuZG9tIHdhbGtzIGludG8gdmlzaXRhdGlvbiBmcmVxdWVuY2llcwp1cmQgPC0gcHJvY2Vzc1JhbmRvbVdhbGtzRnJvbVRpcHModXJkLCB3YWxrcywgdmVyYm9zZSA9IFQpCnNhdmVSRFModXJkLCBmaWxlPSIuL2RhdGEvdXJkLnJkcyIpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShncm9icz1saXN0KAogIHBsb3REaW0odXJkLCAidGlwLm5hbWUiLCBwbG90LnRpdGxlPSJDZWxscyBpbiBlYWNoIHRpcCIpLAogIHBsb3REaW0odXJkLCAidmlzaXRmcmVxLmxvZy4xIiwgdHJhbnNpdGlvbnMucGxvdD0xMDAwMCwgcGxvdC50aXRsZT0idmlzaXRmcmVxX0dDIiksCiAgcGxvdERpbSh1cmQsICJ2aXNpdGZyZXEubG9nLjIiLCB0cmFuc2l0aW9ucy5wbG90PTEwMDAwLCBwbG90LnRpdGxlPSJ2aXNpdGZyZXFfbUNOIiksCiAgcGxvdERpbSh1cmQsICJ2aXNpdGZyZXEubG9nLjMiLCB0cmFuc2l0aW9ucy5wbG90PTEwMDAwLCBwbG90LnRpdGxlPSJ2aXNpdGZyZXFfbENOIikKKSkKYGBgCgojIEJ1aWxkIHRyYWplY3RvcnkgdHJlZQpgYGB7cn0KdXJkLnRyZWUgPC0gbG9hZFRpcENlbGxzKHVyZCwgInRpcC5udW0iKSAKdGlwLmlkIDwtIHNldGRpZmYodW5pcXVlKHVyZC50cmVlQGdyb3VwLmlkcyR0aXAubnVtKSxjKE5BKSkKdXJkLnRyZWUgPC0gYnVpbGRUcmVlKHVyZC50cmVlLCBwc2V1ZG90aW1lID0gInBzZXVkb3RpbWUiLCB0aXBzLnVzZSA9IHRpcC5pZCwgZGl2ZXJnZW5jZS5tZXRob2QgPSAia3MiLCAKICAgICAgICAgICAgICAgICAgICAgIGNlbGxzLnBlci5wc2V1ZG90aW1lLmJpbiA9IDMwLCBiaW5zLnBlci5wc2V1ZG90aW1lLndpbmRvdyA9IDgsIG1pbmltdW0udmlzaXRzID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICB2aXNpdC50aHJlc2hvbGQgPSAwLjcsIHVzZS5vbmx5Lm9yaWdpbmFsLnRpcHMgPSBULAogICAgICAgICAgICAgICAgICAgICAgc2F2ZS5hbGwuYnJlYWtwb2ludC5pbmZvID0gVCwgcC50aHJlc2g9MC4wNSkKIyBOYW1lIHRoZSBzZWdtZW50cwp1cmQudHJlZSA8LSBuYW1lU2VnbWVudHModXJkLnRyZWUsIHNlZ21lbnRzPSB0aXAuaWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgc2VnbWVudC5uYW1lcyA9IGMoIkdDIiwgIm1DTiIsImxDTiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHNob3J0Lm5hbWVzID0gdGlwLmlkKQpgYGAKCiMgUGxvdCByZXN1bHRzCmBgYHtyfQpwbG90VHJlZSh1cmQudHJlZSwgInNlZ21lbnQiLCB0aXRsZT0iVVJEIHRyZWUgc2VnbWVudCIsIGxhYmVsLnNlZ21lbnRzID0gVCkKcGxvdFRyZWUodXJkLnRyZWUsICJpZGVudCIsIHRpdGxlPSJVUkQgY2x1c3RlciBJRCIsIGxhYmVsLnNlZ21lbnRzID0gVCkKYGBgCgojIFBsb3QgZ2VuZSBleHByZXNzaW9uIG92ZXIgdGhlIHRyYWplY3RvcnkKYGBge3J9CmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGdyb2JzPWxpc3QoCiAgcGxvdFRyZWUodXJkLnRyZWUsICJMbXgxYSIsIHRpdGxlPSJtQ04gbWFya2VyX0xteDFhIiksCiAgcGxvdFRyZWUodXJkLnRyZWUsICJPbGlnMiIsIHRpdGxlPSJsQ04gbWFya2VyX09saWcyIiksCiAgcGxvdFRyZWUodXJkLnRyZWUsICJBdG9oMSIsIHRpdGxlPSJHQyBtYXJrZXJfQXRvaDEiKSwKICBwbG90VHJlZSh1cmQudHJlZSwgIldudDEiLCB0aXRsZT0iQzEgbWFya2VyX1dudDEiKQopKQpgYGAKCmBgYHtyfQpzYXZlUkRTKHVyZC50cmVlLCBmaWxlID0gIi4vZGF0YS91cmRUcmVlLnJkcyIpCmBgYAoKIyBFeGFtaW5lIGdlbmUgZXhwcmVzc2lvbiBjYXNjYWRlcyAKYGBge3J9CnVyZC50cmVlIDwtIHJlYWRSRFMoIi4vZGF0YS91cmRUcmVlLnJkcyIpCgpjb2xuYW1lcyh1cmQudHJlZUBtZXRhKSA8LSBjKCJuLkdlbmVzIiwibi5UcmFucyIsIm9yaWcuaWRlbnQiLCJwZXJjZW50Lm1pdG8iLCJ0cmVlLmlkZW50IiwiY2x1c3QiKQoKdGlwcy50by5ydW4gPC0gc2V0ZGlmZihhcy5jaGFyYWN0ZXIodXJkLnRyZWVAdHJlZSRzZWdtZW50Lm5hbWVzKSwgTkEpCmdlbmVzLnVzZSA8LSBOVUxMICMgQ2FsY3VsYXRlIGZvciBhbGwgZ2VuZXMKCiMgQ2FsY3VsYXRlIHRoZSBtYXJrZXJzIG9mIGVhY2ggb3RoZXIgcG9wdWxhdGlvbi4KZ2VuZS5tYXJrZXJzIDwtIGxpc3QoKQptYXJrZXJzLnN1bSA8LSBOVUxMCmZvciAodGlwIGluIHRpcHMudG8ucnVuKSB7CiAgcHJpbnQocGFzdGUwKFN5cy50aW1lKCksICI6ICIsIHRpcCkpCiAgbWFya2VycyA8LSBhdWNwclRlc3RBbG9uZ1RyZWUodXJkLnRyZWUsIHBzZXVkb3RpbWUgPSAicHNldWRvdGltZSIsIHRpcHMgPSB0aXAsIGxvZy5lZmZlY3Quc2l6ZSA9IDAuNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWMuZmFjdG9yID0gMS4yNSwgbWF4LmF1Yy50aHJlc2hvbGQgPSAwLjg1LCBmcmFjLm11c3QuZXhwcmVzcyA9IDAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFjLm1pbi5kaWZmID0gMC4xLCBnZW5lcy51c2UgPSBnZW5lcy51c2UsIHJvb3QgPSBOVUxMLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZ3MudG8uc2tpcCA9IE5VTEwsIG9ubHkucmV0dXJuLmdsb2JhbCA9IEYsIG11c3QuYmVhdC5zaWJzID0gMC42LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcG9ydC5kZWJ1ZyA9IFQpCiAgZ2VuZS5tYXJrZXJzW1t0aXBdXSA8LSBtYXJrZXJzCn0Kc2F2ZVJEUyhnZW5lLm1hcmtlcnMsICIuL2RhdGEvZ2VuZU1hcmtlcnMucmRzIikKCiMgU2VwYXJhdGUgYWN0dWFsIG1hcmtlciBsaXN0cyBmcm9tIHRoZSBzdGF0cyBsaXN0cwpnZW5lLm1hcmtlcnMuZGUgPC0gbGFwcGx5KGdlbmUubWFya2VycywgZnVuY3Rpb24oeCkgeFtbMV1dKQpnZW5lLm1hcmtlcnMuc3RhdHMgPC0gbGFwcGx5KGdlbmUubWFya2VycywgZnVuY3Rpb24oeCkgeFtbMl1dKQpuYW1lcyhnZW5lLm1hcmtlcnMuZGUpIDwtIG5hbWVzKGdlbmUubWFya2VycykKbmFtZXMoZ2VuZS5tYXJrZXJzLnN0YXRzKSA8LSBuYW1lcyhnZW5lLm1hcmtlcnMpCgojIENvbXBpbGUgYWxsIGNvbXBhcmlzb24gc3RhdHMgaW50byBhIHNpbmdsZSB0YWJsZQphbGwuZGUuc3RhdHMgPC0gZG8uY2FsbCgicmJpbmQiLCBnZW5lLm1hcmtlcnMuc3RhdHMpCmFsbC5kZS5zdGF0cyR0aXAgPC0gc3Vic3RyKHJvd25hbWVzKGFsbC5kZS5zdGF0cyksMSxuY2hhcihyb3duYW1lcyhhbGwuZGUuc3RhdHMpKS0yKQoKIyBEbyBhIGZldyBwbG90cwpwMSA8LSBnZ3Bsb3QoYWxsLmRlLnN0YXRzLCBhZXMoeD1wdC4xLm1lYW4sIHk9cHQuMi5tZWFuKSkgKyBnZW9tX3BvaW50KCkgKyB0aGVtZV9idygpICsgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQ9MCwgY29sPSdyZWQnLCBsdHk9MikgKyBsYWJzKHg9Ik1lYW4gUHNldWRvdGltZSAoR3JvdXAgMSkiLCB5PSJNZWFuIFBzZXVkb3RpbWUgKEdyb3VwIDIpIikKcDIgPC0gZ2dwbG90KGFsbC5kZS5zdGF0cywgYWVzKHg9Z2VuZXMuMS5tZWFuLCB5PWdlbmVzLjIubWVhbikpICsgZ2VvbV9wb2ludCgpICsgdGhlbWVfYncoKSArIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0PTAsIGNvbD0ncmVkJywgbHR5PTIpICsgbGFicyh4PSJNZWFuIERldGVjdGVkIEdlbmVzIChHcm91cCAxKSIsIHk9Ik1lYW4gRGV0ZWN0ZWQgR2VuZXMgKEdyb3VwIDIpIikKcDMgPC0gZ2dwbG90KGFsbC5kZS5zdGF0cywgYWVzKHg9dHJhbnMuMS5tZWFuLCB5PXRyYW5zLjIubWVhbikpICsgZ2VvbV9wb2ludCgpICsgdGhlbWVfYncoKSArIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0PTAsIGNvbD0ncmVkJywgbHR5PTIpICsgbGFicyh4PSJNZWFuIFRyYW5zY3JpcHRzIChHcm91cCAxKSIsIHk9Ik1lYW4gVHJhbnNjcmlwdHMgKEdyb3VwIDIpIikKCnBsb3RfZ3JpZChwMSxwMixwMywgbmNvbCA9IDMpCgoKIyBDcmVhdGUgYSBmb2xkIHRvIGhvbGQgdGhlIHJlc3VsdHMKcGF0aCA8LSAiLmRhdGEvIgpkaXIuY3JlYXRlKHBhc3RlMChwYXRoLCJjYXNjYWRlcyIpKQoKIyBHZW5lcmF0ZSBpbXB1bHNlIGZpdHMKZ2VuZS5jYXNjYWRlcyA8LSBsYXBwbHkodGlwcy50by5ydW4sIGZ1bmN0aW9uKHRpcCkgewogIHByaW50KHBhc3RlMChTeXMudGltZSgpLCAiOiBJbXB1bHNlIEZpdCAiLCB0aXApKQogIHNlZy5jZWxscyA8LSBjZWxsc0Fsb25nTGluZWFnZSh1cmQudHJlZSwgdGlwLCByZW1vdmUucm9vdD1GKQogIGNhc2MgPC0gZ2VuZUNhc2NhZGVQcm9jZXNzKG9iamVjdCA9IHVyZC50cmVlLCBwc2V1ZG90aW1lPSdwc2V1ZG90aW1lJywgY2VsbHMgPSBzZWcuY2VsbHMsIGdlbmVzPSByb3duYW1lcyhnZW5lLm1hcmtlcnMuZGVbW3RpcF1dKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW92aW5nLndpbmRvdz0zLCBjZWxscy5wZXIud2luZG93PTEwLCBsaW1pdC5zaW5nbGUuc2lnbW9pZC5zbG9wZXMgPSAib24iLCB2ZXJib3NlID0gVCkKICB0aXAuZmlsZS5uYW1lIDwtIGdzdWIoIi8iLCAiXyIsIHRpcCkKICBzYXZlUkRTKGNhc2MsIGZpbGU9cGFzdGUwKHBhdGgsICJjYXNjYWRlcy9jYXNjXyIsIHRpcC5maWxlLm5hbWUsICIucmRzIikpCiAgcmV0dXJuKGNhc2MpCn0pCm5hbWVzKGdlbmUuY2FzY2FkZXMpIDwtIHRpcHMudG8ucnVuCgojIE1ha2UgYSBoZWF0bWFwIG9mIGV2ZXJ5IGNhc2NhZGUgaW4gYSBzaW5nbGUgUERGLgpmb3IgKHRpcCBpbiB0aXBzLnRvLnJ1bikgewogIGdlbmUubnVtIDwtIG5yb3coZ2VuZS5jYXNjYWRlc1tbdGlwXV0kc2NhbGVkLmV4cHJlc3Npb24pCiAgZ2VuZUNhc2NhZGVIZWF0bWFwKGNhc2NhZGU9Z2VuZS5jYXNjYWRlc1tbdGlwXV0sIHRpdGxlID0gdGlwLCByb3cuZm9udC5zaXplID0gMC4wNipnZW5lLm51bSkKfQoKIyA9PT09PXJlcGVhdCB3aXRoIFRGIGdlbmVzIG9ubHkgPT09PT09PT09PT09CiMgSWRlbnRpZnkgREUgZ2VuZXMgb2YgZWFjaCBvdGhlciBwb3B1bGF0aW9uIChyZXN0cmljdGVkIHRvIHRyYW5zY3JpcHRpb24gZmFjdG9ycyBvbmx5KQptR2VuZXMgPC0gcmVhZFJEUygiL1ZvbHVtZXMvamFsaS9HZW5vbWUvQW5ub3RhdGlvbjEucmRzIikKVEYudXNlIDwtIGludGVyc2VjdChyb3duYW1lcyh1cmRAbG9ndXB4LmRhdGEpLG1HZW5lc1t3aGljaChtR2VuZXMkVHlwZSA+ICJub25URiIpLCAibWdpX3N5bWJvbCJdKTtsZW5ndGgoVEYudXNlKQoKVEYubWFya2VycyA8LSBsaXN0KCkKZm9yICh0aXBuIGluIDE6bGVuZ3RoKHRpcHMudG8ucnVuKSkgewogIHRpcCA8LSB0aXBzLnRvLnJ1blt0aXBuXQogIHByaW50KHBhc3RlMChTeXMudGltZSgpLCAiOiAiLCB0aXApKQogIG1hcmtlcnMgPC0gYXVjcHJUZXN0QWxvbmdUcmVlKHVyZC50cmVlLCBwc2V1ZG90aW1lID0gInBzZXVkb3RpbWUiLCB0aXBzID0gdGlwLCBsb2cuZWZmZWN0LnNpemUgPSAwLjI1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1Yy5mYWN0b3IgPSAxLjI1LCBtYXguYXVjLnRocmVzaG9sZCA9IDAuODUsIGZyYWMubXVzdC5leHByZXNzID0gMC4xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyYWMubWluLmRpZmYgPSAwLjEsIGdlbmVzLnVzZSA9IFRGLnVzZSwgcm9vdCA9IE5VTEwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2Vncy50by5za2lwID0gTlVMTCwgb25seS5yZXR1cm4uZ2xvYmFsID0gRiwgbXVzdC5iZWF0LnNpYnMgPSAwLjYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwb3J0LmRlYnVnID0gVCkKICBURi5tYXJrZXJzW1t0aXBdXSA8LSBtYXJrZXJzCn0Kc2F2ZVJEUyhURi5tYXJrZXJzLCAiLi9kYXRhL2dlbmVNYXJrZXJzX1RGLnJkcyIpCgojID09PT09PSBHZW5lcmF0ZSBpbXB1bHNlIGZpdHMgZm9yIERFIHRyYW5zY3JpcHRpb24gZmFjdG9ycyA9PT09PT0gClRGLm1hcmtlcnMuZGUgPC0gbGFwcGx5KFRGLm1hcmtlcnMsIGZ1bmN0aW9uKHgpIHhbWzFdXSkKbmFtZXMoVEYubWFya2Vycy5kZSkgPC0gbmFtZXMoVEYubWFya2VycykKClRGLmNhc2NhZGVzIDwtIGxhcHBseSh0aXBzLnRvLnJ1biwgZnVuY3Rpb24odGlwKSB7CiAgcHJpbnQocGFzdGUwKFN5cy50aW1lKCksICI6IEltcHVsc2UgRml0ICIsIHRpcCkpCiAgc2VnLmNlbGxzIDwtIGNlbGxzQWxvbmdMaW5lYWdlKHVyZC50cmVlLCB0aXAsIHJlbW92ZS5yb290PUYpCiAgY2FzYyA8LSBnZW5lQ2FzY2FkZVByb2Nlc3Mob2JqZWN0ID0gdXJkLnRyZWUsIHBzZXVkb3RpbWU9J3BzZXVkb3RpbWUnLCBjZWxscyA9IHNlZy5jZWxscywgZ2VuZXM9IHJvd25hbWVzKFRGLm1hcmtlcnMuZGVbW3RpcF1dKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBiYWNrZ3JvdW5kLmdlbmVzID0gc2FtcGxlKHNldGRpZmYocm93bmFtZXModXJkLnRyZWVAbG9ndXB4LmRhdGEpLHVyZC50cmVlQHZhci5nZW5lcyksIDEwMDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vdmluZy53aW5kb3c9MywgY2VsbHMucGVyLndpbmRvdz0xMCwgbGltaXQuc2luZ2xlLnNpZ21vaWQuc2xvcGVzID0gIm9uIiwgdmVyYm9zZSA9IFQpCiAgcmV0dXJuKGNhc2MpCn0pCm5hbWVzKFRGLmNhc2NhZGVzKSA8LSB0aXBzLnRvLnJ1bgpzYXZlUkRTKFRGLmNhc2NhZGVzLCAiLi9kYXRhL1RGLmNhc2NhZGVzLnJkcyIpCgojIE1ha2UgYSBoZWF0bWFwIG9mIGV2ZXJ5IGNhc2NhZGUgaW4gYSBzaW5nbGUgUERGLgpwZGYoZmlsZT1wYXN0ZTAocGF0aCwiY2FzY2FkZXMvY2FzY2FkZXNfVEYucGRmIiksIHdpZHRoPTUsIGhlaWdodD02KQpmb3IgKHRpcCBpbiB0aXBzLnRvLnJ1bikgewogIGdlbmUubnVtIDwtIG5yb3coVEYuY2FzY2FkZXNbW3RpcF1dJHNjYWxlZC5leHByZXNzaW9uKQogIGdlbmVDYXNjYWRlSGVhdG1hcChjYXNjYWRlPVRGLmNhc2NhZGVzW1t0aXBdXSwgdGl0bGUgPSB0aXAsIHJvdy5mb250LnNpemUgPSAwLjYqZ2VuZS5udW0pCn0KZGV2Lm9mZigpCgojIGNyZWF0ZSBhIHRhYmxlIG9mIG1hcmtlcnMgZ2VuZXMgd2l0aCB0aW1pbmcKbWFya2Vycy5zdW0gPC0gTlVMTApmb3IgKHRpcCBpbiB0aXBzLnRvLnJ1bikgewogIHJlcyA8LSBnZW5lLm1hcmtlcnMuZGVbW3RpcF1dCiAgcmVzJHRpcCA8LSB0aXAKICBjYXNjYWRlPWdlbmUuY2FzY2FkZXNbW3RpcF1dCiAgCiAgIyBDb3JyZWN0IGZvciBOQSB0aW1pbmdzCiAgdGltaW5nIDwtIGNhc2NhZGUkdGltaW5nCiAgdGltaW5nW2ludGVyc2VjdCh3aGljaChpcy5uYSh0aW1pbmckdGltZS5vbikpLCB3aGljaChpcy5pbmZpbml0ZSh0aW1pbmckdGltZS5vZmYpKSksICJ0aW1lLm9uIl0gPC0gSW5mCiAgcmVzIDwtIGNiaW5kKHJlcyx0aW1pbmcpCiAgZ2VuZS5vcmRlciA8LSBvcmRlcih0aW1pbmckdGltZS5vbiwgdGltaW5nJHRpbWUub2ZmLCBuYS5sYXN0PUYpCiAgcmVzIDwtIHJlc1tnZW5lLm9yZGVyLF0KICByZXMkZ2VuZSA8LSByb3duYW1lcyhyZXMpCiAgcmVzJFRGIDwtIG1HZW5lcyRUeXBlW21hdGNoKHJlcyRnZW5lLG1HZW5lcyRtZ2lfc3ltYm9sKV0KICByZXMkZGVzY3JpcHRpb24gPC0gbUdlbmVzJGRlc2NyaXB0aW9uW21hdGNoKHJlcyRnZW5lLG1HZW5lcyRtZ2lfc3ltYm9sKV0KICBtYXJrZXJzLnN1bSA8LSByYmluZChtYXJrZXJzLnN1bSwgcmVzKQp9CmhlYWQobWFya2Vycy5zdW0pCgoKIyMjID09PT09PSBJZGVudGlmeSBtYXJrZXJzIG9mIGVhY2ggbGluZWFnZSA9PT09PT0gCm1hcmtlcnMuc3VtIDwtIE5VTEwKdGlwIDwtICJHQyIKdGlwLmZpbGUubmFtZSA8LSBnc3ViKCIvIiwgIl8iLCB0aXApCmNhc2NhZGUgPC0gcmVhZFJEUyhmaWxlID0gcGFzdGUwKCIuL2Nhc2NhZGVzL2Nhc2NfIiwgdGlwLmZpbGUubmFtZSwgIi5yZHMiKSkKbWFya2VycyA8LSByb3duYW1lcyhjYXNjYWRlJHNjYWxlZC5leHByZXNzaW9uKQoKIyBEZXRlcm1pbmUgd2hpY2ggZ2VuZXMgYXJlIGFsc28gZ2xvYmFsIG1hcmtlcnMKY2VsbHMgPC0gY2VsbHNJbkNsdXN0ZXIodXJkLnRyZWUsICJzZWdtZW50IiwgYygiMSIpKQptYXJrZXJzLmdsb2JhbCA8LSBtYXJrZXJzQVVDUFIodXJkLnRyZWUsIGNlbGxzLjEgPSBjZWxscywgY2VsbHMuMiA9IE5VTEwsIGdlbmVzLnVzZSA9IG1hcmtlcnMsIGNsdXN0ZXJpbmcgPSAic2VnbWVudCIpCm1hcmtlci50aHJlc2ggPC0gYXVjcHJUaHJlc2hvbGQoY2VsbHMuMSA9IGNlbGxzLCBjZWxscy4yID0gc2V0ZGlmZih1bmxpc3QodXJkLnRyZWVAdHJlZSRjZWxscy5pbi5zZWdtZW50KSwgY2VsbHMpLCBmYWN0b3IgPSAyLjUsIG1heC5hdWMgPSBJbmYpICMgbG93ZXIgdGhlIHN0cmluZ2VuY3kgYnkgcmVkdWNpbmcgdGhlIGZhY3RvciAKZGUubWFya2VycyA8LSBtYXJrZXJzLmdsb2JhbFttYXJrZXJzLmdsb2JhbCRBVUNQUiA+PSBtYXJrZXIudGhyZXNoLF07bnJvdyhkZS5tYXJrZXJzKQpkZS5tYXJrZXJzJHRpcCA8LSB0aXAKZGUubWFya2VycyRnZW5lIDwtIHJvd25hbWVzKGRlLm1hcmtlcnMpCmRlLm1hcmtlcnMkVEYgPC0gbUdlbmVzJFR5cGVbbWF0Y2goZGUubWFya2VycyRnZW5lLG1HZW5lcyRtZ2lfc3ltYm9sKV0KZGUubWFya2VycyRkZXNjcmlwdGlvbiA8LSBtR2VuZXMkZGVzY3JpcHRpb25bbWF0Y2goZGUubWFya2VycyRnZW5lLG1HZW5lcyRtZ2lfc3ltYm9sKV0KbWFya2Vycy5zdW0gPC0gcmJpbmQobWFya2Vycy5zdW0sIGRlLm1hcmtlcnMpCgojIGZpbmQgbWFya2VyIG9mIGNhdWRhbCB0aGFsYW11cwp0aXAgPC0gImxDTiIKdGlwLmZpbGUubmFtZSA8LSBnc3ViKCIvIiwgIl8iLCB0aXApCmNhc2NhZGUgPC0gcmVhZFJEUyhmaWxlID0gcGFzdGUwKCIuL2Nhc2NhZGVzL2Nhc2NfIiwgdGlwLmZpbGUubmFtZSwgIi5yZHMiKSkKbWFya2VycyA8LSByb3duYW1lcyhjYXNjYWRlJHNjYWxlZC5leHByZXNzaW9uKQpjZWxscyA8LSBjZWxsc0luQ2x1c3Rlcih1cmQudHJlZSwgInNlZ21lbnQiLCBjKCIyIikpCm1hcmtlcnMuZ2xvYmFsIDwtIG1hcmtlcnNBVUNQUih1cmQudHJlZSwgY2VsbHMuMSA9IGNlbGxzLCBnZW5lcy51c2UgPSBtYXJrZXJzLCBjbHVzdGVyaW5nID0gInNlZ21lbnQiKQptYXJrZXIudGhyZXNoIDwtIGF1Y3ByVGhyZXNob2xkKGNlbGxzLjEgPSBjZWxscywgY2VsbHMuMiA9IHNldGRpZmYodW5saXN0KHVyZC50cmVlQHRyZWUkY2VsbHMuaW4uc2VnbWVudCksIGNlbGxzKSwgZmFjdG9yID0gMi41LCBtYXguYXVjID0gSW5mKSAKZGUubWFya2VycyA8LSBtYXJrZXJzLmdsb2JhbFttYXJrZXJzLmdsb2JhbCRBVUNQUiA+PSBtYXJrZXIudGhyZXNoLF07bnJvdyhkZS5tYXJrZXJzKQpkZS5tYXJrZXJzJHRpcCA8LSB0aXAKZGUubWFya2VycyRnZW5lIDwtIHJvd25hbWVzKGRlLm1hcmtlcnMpCmRlLm1hcmtlcnMkVEYgPC0gbUdlbmVzJFR5cGVbbWF0Y2goZGUubWFya2VycyRnZW5lLG1HZW5lcyRtZ2lfc3ltYm9sKV0KZGUubWFya2VycyRkZXNjcmlwdGlvbiA8LSBtR2VuZXMkZGVzY3JpcHRpb25bbWF0Y2goZGUubWFya2VycyRnZW5lLG1HZW5lcyRtZ2lfc3ltYm9sKV0KbWFya2Vycy5zdW0gPC0gcmJpbmQobWFya2Vycy5zdW0sIGRlLm1hcmtlcnMpCgojIGZpbmQgbWFya2VyIG9mIGNhdWRhbCB0aGFsYW11cwp0aXAgPC0gIm1DTiIKdGlwLmZpbGUubmFtZSA8LSBnc3ViKCIvIiwgIl8iLCB0aXApCmNhc2NhZGUgPC0gcmVhZFJEUyhmaWxlID0gcGFzdGUwKCIuL2Nhc2NhZGVzL2Nhc2NfIiwgdGlwLmZpbGUubmFtZSwgIi5yZHMiKSkKbWFya2VycyA8LSByb3duYW1lcyhjYXNjYWRlJHNjYWxlZC5leHByZXNzaW9uKQpjZWxscyA8LSBjZWxsc0luQ2x1c3Rlcih1cmQudHJlZSwgInNlZ21lbnQiLCBjKCIzIikpCm1hcmtlcnMuZ2xvYmFsIDwtIG1hcmtlcnNBVUNQUih1cmQudHJlZSwgY2VsbHMuMSA9IGNlbGxzLCBjZWxscy4yID0gTlVMTCwgZ2VuZXMudXNlID0gbWFya2VycywgY2x1c3RlcmluZyA9ICJzZWdtZW50IikKbWFya2VyLnRocmVzaCA8LSBhdWNwclRocmVzaG9sZChjZWxscy4xID0gY2VsbHMsIGNlbGxzLjIgPSBzZXRkaWZmKHVubGlzdCh1cmQudHJlZUB0cmVlJGNlbGxzLmluLnNlZ21lbnQpLCBjZWxscyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhY3RvciA9IDIuNSwgbWF4LmF1YyA9IEluZikgIyBsb3dlciB0aGUgc3RyaW5nZW5jeSBieSByZWR1Y2luZyB0aGUgZmFjdG9yIApkZS5tYXJrZXJzIDwtIG1hcmtlcnMuZ2xvYmFsW21hcmtlcnMuZ2xvYmFsJEFVQ1BSID49IG1hcmtlci50aHJlc2gsXTtucm93KGRlLm1hcmtlcnMpCmRlLm1hcmtlcnMkdGlwIDwtIHRpcApkZS5tYXJrZXJzJGdlbmUgPC0gcm93bmFtZXMoZGUubWFya2VycykKZGUubWFya2VycyRURiA8LSBtR2VuZXMkVHlwZVttYXRjaChkZS5tYXJrZXJzJGdlbmUsbUdlbmVzJG1naV9zeW1ib2wpXQpkZS5tYXJrZXJzJGRlc2NyaXB0aW9uIDwtIG1HZW5lcyRkZXNjcmlwdGlvblttYXRjaChkZS5tYXJrZXJzJGdlbmUsbUdlbmVzJG1naV9zeW1ib2wpXQptYXJrZXJzLnN1bSA8LSByYmluZChtYXJrZXJzLnN1bSwgZGUubWFya2VycykKCiMgb3Blbnhsc3g6OndyaXRlLnhsc3gobWFya2Vycy5zdW0sIGZpbGUgPSAiLi90YWJsZXMvc2VnbWFudE1hcmtlcnNfYWxsLnhsc3giKQpvcGVueGxzeDo6d3JpdGUueGxzeChtYXJrZXJzLnN1bSwgZmlsZSA9ICIuL3RhYmxlcy9saW5lYWdlTWFya2Vyc19hbGwueGxzeCIpCmBgYAoKIyBQcmludCBzZXNzaW9uIGluZm9ybWF0aW9uCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK